package jglob;

import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @author sergei.g
 */
public class JGlob {

    /**
     * Initializes a newly created JGlob object so that it represents
     * an empty search Path.
     */
    public JGlob() {
    }

    /**
     * Return a list of paths matching a pathname pattern.
     * The pattern may contain simple shell-style wildcards a la fnmatch.
     *
     * @param path search path supported wildcarts.
     * @return the Files/Dictories as String array.
     */
    public final String[] glob(final String path) {
        List<String> paths = new LinkedList<String>();
        final String magicCheck = ".*[\\*\\?\\[].*";
        String normPath = normPath(path);

        File file = new File(normPath);
        if (!path.matches(magicCheck)) {
            if (file.exists()) {
                paths.add(path);
            }

            return (String[]) paths.toArray(new String[paths.size()]);
        }

        IInnerGlob innerGlob = new InnerGlob1();

        String basename = file.getName();
        String dirname = normPath(file.getParent());

        if (dirname.isEmpty()) {
            for (String elm : innerGlob.glob(".", basename)) {
                paths.add(elm);
            }

            return (String[]) paths.toArray(new String[paths.size()]);
        }

        String[] dirs = {dirname};
        if (dirname.matches(magicCheck)) {
            dirs = glob(dirname);
        }

        if (!basename.matches(magicCheck)) {
            innerGlob = new InnerGlob0();
        }

        for (String dir : dirs) {
            for (String name : innerGlob.glob(dir, basename)) {
                paths.add(dir + "/" + name);
            }
        }

        return (String[]) paths.toArray(new String[paths.size()]);
    }

    /**
     * Test whether FILENAME matches PATTERN.
    Patterns are Unix shell style:

     *       matches everything
    ?       matches any single character
    [seq]   matches any character in seq
    [!seq]  matches any char not in seq

    An initial period in FILENAME is not special.
    Both FILENAME and PATTERN are first case-normalized
    if the operating system requires it.
    If you don't want this, use fnmatchcase(FILENAME, PATTERN).
     *
     * @param fn filename
     * @param pat search pattern
     * @return true by success
     */
    public final boolean fnmatch(final String fn, final String pat) {
        if (fn == null || pat == null) {
            throw new IllegalArgumentException("Arguments can't be null.");
        }

        String normpat = normPath(pat);
        normpat = translate(normpat);

        String normfn = normPath(fn);
        return normfn.matches(normpat);
    }

    /**
     * Return the subset of the list NAMES that match PAT.
     * @param names names list
     * @param pat search pattern
     * @return matched names
     */
    protected final String[] filter(final String[] names, final String pat) {
        if (names == null || pat == null) {
            throw new IllegalArgumentException("Arguments can't be null.");
        }

        List<String> result = new LinkedList<String>();

        String normpat = normPath(pat);
        normpat = translate(normpat);

        for (String name : names) {
            String norm = normPath(name);
            if (norm.matches(normpat)) {
                result.add(name);
            }
        }

        return (String[]) result.toArray(new String[result.size()]);
    }

    /**
     * Normalize Path separator by '/'.
     * @param path search path
     * @return path as string
     */
    private static String normPath(final String path) {
        return path == null ? "" : path.trim().replace("\\", "/").toLowerCase();
    }

    /**
     * Translate a shell PATTERN to a regular expression.
     * There is no way to quote meta-characters.
     * @param pat search pattern
     * @return translated unix search pattern to regular expression.
     */
    private static String translate(final String pat) {
        String res = "";
        int n = pat.length();
        int i = 0;

        while (i < n) {
            char c = pat.charAt(i);
            i++;

            if (c == '*') {
                res = res + ".*";
            } else if (c == '?') {
                res = res + ".";
            } else if (c == '[') {
                int j = i;

                if (j < n && pat.charAt(j) == '!') {
                    j++;
                }

                if (j < n && pat.charAt(j) == ']') {
                    j++;
                }

                while (j < n && pat.charAt(j) != ']') {
                    j++;
                }

                if (j >= n) {
                    res = res + "\\[";
                } else {
                    String stuff = pat.substring(i, j).replace("\\", "\\\\");

                    i = j + 1;
                    if (stuff.charAt(0) == '!') {
                        stuff = '^' + stuff.substring(1);
                    } else if (stuff.charAt(0) == '^') {
                        stuff = '\\' + stuff;
                    }

                    res = res + '[' + stuff + ']';
                }

            } else {
                res = res + Pattern.quote(Character.toString(c));
            }
        }

        return res + "$";
    }

    /**
     * IInnerGlob java way of delegate.
     */
    interface IInnerGlob {

        /**
         * glob.
         * @param p1 parm1
         * @param p2 parm2
         * @return file/dir list
         */
        String[] glob(String p1, String p2);
    }

    /**
     * InnerGlob1.
     */
    static class InnerGlob0 implements IInnerGlob {

        @Override
        public String[] glob(final String dirname, final String basename) {
            File file = new File(dirname + "/" + basename);
            if (file.exists()) {
                return new String[]{basename};
            }

            return new String[]{};
        }
    }

    /**
     * InnerGlob1.
     */
    class InnerGlob1 implements IInnerGlob {

        @Override
        public String[] glob(final String dirname, final String pattern) {
            String[] names = new File(dirname).list();
            if (names == null) {
                return new String[]{};
            }

            return filter(names, pattern);
        }
    }
}
